/*
** Text-Based Gaming System 
** Copyright (C) 2008 Eric Day
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to:
** Free Software Foundation, Inc.
** 51 Franklin Street, Fifth Floor,
** Boston, MA  02110-1301, USA
*/

#include "tgsd.h"

/*
** static functions
*/

/* parse and handle user input */
int _user_parse(TGSD *tgsd, TGSD_CON *con, char *buf, int len);

/* allocate a new connection */
int _con_alloc(TGSD *tgsd, int *index);

/* allocate a new game session */
int _game_session_alloc(TGSD *tgsd, int *index);

/* find a game module given a name */
static TGSD_GAME *_game_find(char *name);

/* print usage information */
void _usage(char *name);

/* static variables */
static TGSD_GAME *_game_list[] =
{
    &tgsd_draw,
    &tgsd_sudoku,
    NULL
};

/*
** main function
*/

int main(int argc, char *argv[])
{
    char            c;
    int             ret;
    TGSD            tgsd;
    struct timeval  tv;
    TGSD_CON       *con;
    char            buf[TGS_STR_LEN];
    int             len;
    int             x;
    int             y;

    memset(&tgsd, 0, sizeof(TGSD));
    tgsd.port = TGS_DEFAULT_PORT;

    while ((c = getopt(argc, argv, "p:h")) != EOF)
    {
        switch(c)
        {
        case 'p':
            tgsd.port = atoi(optarg);
            break;

        case 'h':
        default:
            _usage(argv[0]);
            return 0;
        }
    }

    /* set any signal handlers */
    TGSD_RET_CHECK(signal(SIGPIPE, SIG_IGN) == SIG_ERR, "signal", errno);

    ret = _con_alloc(&tgsd, &x);
    TGSD_RET_CHECK(ret, "_con_alloc", ret);

    /* start listening connection */
    ret = lpc_listen(&(tgsd.con[0].lpc), &(tgsd.pfd[0]), tgsd.port, 10);
    TGSD_RET_CHECK(ret, "lpc_init", ret);

    while (1)
    {
        ret = gettimeofday(&tv, NULL);
        TGSD_RET_CHECK(ret == -1, "gettimeofday", errno);

        /* wait for socket activity */
        ret = poll(tgsd.pfd, tgsd.con_num, 1000);
        TGSD_RET_CHECK(ret == -1 && errno != EINTR, "poll", errno);

        for (x = 0; x < tgsd.con_num; x++)
        {
            con = &(tgsd.con[x]);

            if (con->lpc == NULL || tgsd.pfd[x].revents == 0)
                continue;

            if (x == 0)
            {
                ret = lpc_accept(con->lpc, &len);
                TGSD_RET_CHECK(ret, "lpc_accept", ret);

                ret = _con_alloc(&tgsd, &y);
                TGSD_RET_CHECK(ret, "_con_alloc", ret);

                con = &(tgsd.con[y]);

                ret = lpc_create(&(con->lpc), &(tgsd.pfd[y]), len);
                TGSD_RET_CHECK(ret, "lpc_create", ret);

                con->name[0] = 0;
                con->gs = NULL;

                printf("Connection Accepted (fd=%d)\n", len);

                lpc_printf(con->lpc, "STATUS Welcome to TGS!");

                continue;
            }

            ret = lpc_activity(con->lpc);
            TGSD_RET_CHECK(ret, "lpc_activity", ret);

            if (tgsd.pfd[x].fd == -1)
            {
                printf("Connection Closed (%s)\n", lpc_status(con->lpc));
                if (con->gs)
                {
                    ret = con->gs->game->part(con);
                    TGSD_RET_CHECK(ret, "game->part", ret);
                }

                lpc_destroy(con->lpc);
                con->lpc = NULL;
                continue;
            }

            while (1)
            {
                len = lpc_read(con->lpc, buf, TGS_STR_LEN);

                if (len == -1)
                    break;

                printf("Received Message (%s)\n", buf);

                ret = _user_parse(&tgsd, con, buf, len);
                TGSD_RET_CHECK(ret, "_user_parse", ret);
            }
        }
    }

    return 0;
}

/*
** global functions
*/

/*
** static functions
*/

/* parse and handle user input */
int _user_parse(TGSD *tgsd, TGSD_CON *con, char *buf, int len)
{
    int   ret;
    char *tok;
    char *a;
    int   x;

    tok = strtok(buf, " \t");
    if (tok == NULL)
        return 0;

    if (con->name[0] == 0)
    {
        if (strcasecmp(tok, "NAME"))
        {
            lpc_printf(con->lpc, "NAME ERR Must Give Name First");
            return 0;
        }

        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "NAME ERR Invalid NAME Command");

        ret = strlen(tok);
        if (ret > TGS_NAME_LEN || ret != strspn(tok,
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"))
        {
            lpc_printf(con->lpc, "NAME ERR Invalid Name");
            return 0;
        }

        for (x = 1; x < tgsd->con_num; x++)
        {
            if (tgsd->con[x].lpc == NULL || tgsd->con[x].name[0] == 0)
                continue;

            if (!strcasecmp(tgsd->con[x].name, tok))
            {
                lpc_printf(con->lpc, "NAME ERR Name Already In Use");
                return 0;
            }
        }

        lpc_printf(con->lpc, "STATUS Current Users Are:");

        for (x = 1; x < tgsd->con_num; x++)
        {
            if (tgsd->con[x].lpc == NULL || tgsd->con[x].name[0] == 0)
                continue;

            lpc_printf(con->lpc, "STATUS %s", tgsd->con[x].name);
            lpc_printf(tgsd->con[x].lpc, "STATUS %s Has Joined", tok);
        }

        lpc_printf(con->lpc, "STATUS Current Games Are:");

        for (x = 0; x < tgsd->gs_size; x++)
        {
            if (tgsd->gs[x].game == NULL)
                continue;

            lpc_printf(con->lpc, "STATUS /join %s %d", tgsd->gs[x].game->name,
                x);
        }

        strcpy(con->name, tok);
        lpc_printf(con->lpc, "NAME OK");

        return 0;
    }

    if (!strcasecmp(tok, "MSG"))
    {
        a = strtok(NULL, " \t");
        TGSD_TOK_CHECK(con, a, "STATUS Invalid MSG Command");

        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "STATUS Invalid MSG Command");

        if (strcmp(a, "*"))
            ret = 0;
        else
            ret = 1;

        for (x = 1; x < tgsd->con_num; x++)
        {
            if (tgsd->con[x].lpc == NULL || tgsd->con[x].name[0] == 0)
                continue;

            if (con != &(tgsd->con[x]) && tgsd->con[x].name[0] != 0 &&
                (ret || !strcasecmp(a, tgsd->con[x].name)))
            {
                lpc_printf(tgsd->con[x].lpc, "MSG %s %s %s", con->name, a, tok);
                if (!ret)
                    return 0;
            }
        }

        if (!ret)
            lpc_printf(con->lpc, "STATUS User %s Not Found", a);
    }
    else if (!strcasecmp(tok, "START"))
    {
        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid START Command");

        ret = _game_session_alloc(tgsd, &x);
        TGSD_RET_CHECK(ret, "_game_session_alloc", ret);

        tgsd->gs[x].game = _game_find(tok);
        if (tgsd->gs[x].game == NULL)
        {
            lpc_printf(con->lpc, "GAME ERR Game Not Found");
            return 0;
        }

        con->gs = &(tgsd->gs[x]);

        ret = con->gs->game->start(con);
        TGSD_RET_CHECK(ret, "game->start", ret);

        ret = x;

        for (x = 1; x < tgsd->con_num; x++)
        {
            if (tgsd->con[x].lpc == NULL || con == &(tgsd->con[x]) ||
                tgsd->con[x].name[0] == 0)
            {
                continue;
            }

            lpc_printf(tgsd->con[x].lpc,
                "STATUS New %s Game! To Join Type: /join %s %d",
                con->gs->game->name, con->gs->game->name, ret);
        }
    }
    else if (!strcasecmp(tok, "JOIN"))
    {
        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid JOIN Command");

        x = atoi(tok);

        if (x < 0 || x >= tgsd->gs_num || tgsd->gs[x].game == NULL)
        {
            lpc_printf(con->lpc, "GAME ERR Game Session Not Found");
            return 0;
        }

        con->gs = &(tgsd->gs[x]);
        ret = con->gs->game->join(con);
        TGSD_RET_CHECK(ret, "game->join", ret);
    }
    else if (!strcasecmp(tok, "PART"))
    {
        if (con->gs)
        {
            ret = con->gs->game->part(con);
            TGSD_RET_CHECK(ret, "game->part", ret);
        }
    }
    else if (!strcasecmp(tok, "GAME"))
    {
        tok = strtok(NULL, "");
        TGSD_TOK_CHECK(con, tok, "GAME ERR Invalid GAME Command");

        if (con->gs)
        {
            ret = con->gs->game->data(con, tok);
            TGSD_RET_CHECK(ret, "game->data", ret);
        }
    }
    else
        lpc_printf(con->lpc, "STATUS Invalid Command");

    return 0;
}

/* allocate a new connection */
int _con_alloc(TGSD *tgsd, int *index)
{
    TGSD_CON      *con;
    struct pollfd *pfd;
    int            x;

    for (x = 0; x < tgsd->con_size; x++)
    {
        if (tgsd->con[x].lpc == NULL)
        {
            if (x == tgsd->con_num)
                tgsd->con_num++;

            *index = x;
            return 0;
        }
    }

    con = realloc(tgsd->con, (tgsd->con_size + 16) * sizeof(TGSD_CON));
    TGSD_RET_CHECK(con == NULL, "realloc", errno);

    pfd = realloc(tgsd->pfd, (tgsd->con_size + 16) * sizeof(struct pollfd));
    TGSD_RET_CHECK(pfd == NULL, "realloc", errno);

    for (x = tgsd->con_size; x < tgsd->con_size + 16; x++)
        con[x].lpc = NULL;

    tgsd->con = con;
    tgsd->pfd = pfd;
    tgsd->con_size += 16;
    *index = tgsd->con_num;
    tgsd->con_num++;

    return 0;
}

/* allocate a new game session */
int _game_session_alloc(TGSD *tgsd, int *index)
{
    TGSD_GAME_SESSION *gs;
    int                x;

    for (x = 0; x < tgsd->gs_size; x++)
    {
        if (tgsd->gs[x].game == NULL)
        {
            if (x == tgsd->gs_num)
                tgsd->gs_num++;

            *index = x;
            return 0;
        }
    }

    gs = realloc(tgsd->gs, (tgsd->gs_size + 16) * sizeof(TGSD_GAME_SESSION));
    TGSD_RET_CHECK(gs == NULL, "realloc", errno);

    for (x = tgsd->gs_size; x < tgsd->gs_size + 16; x++)
        gs[x].game = NULL;

    tgsd->gs = gs;
    tgsd->gs_size += 16;
    *index = tgsd->gs_num;
    tgsd->gs_num++;

    return 0;
}

/* find a game module given a name */
static TGSD_GAME *_game_find(char *name)
{
    int x;

    for (x = 0; _game_list[x]; x++)
    {
        if (!strcasecmp(_game_list[x]->name, name))
            return _game_list[x];
    }

    return NULL;
}

/* print usage information */
void _usage(char *name)
{           
    fprintf(stderr, "usage: %s [-h] [-p <port>]\n", name);
    fprintf(stderr, "    -p <port>    port to listen on\n");
}
